﻿using Net.AI;
using Net.AOI;
using Net.Share;
using Net.System;
using Net.Helper;
using Net.Common;
using Net.Entities;
using Net.SceneUtility;
using System;
using System.Reflection;

namespace ActorSystem
{
    public delegate void OperCommandDelegate(in Operation opt);

    public class Scene<T> : World where T : ActorBase
    {
        public MyDictionary<long, T> Actors { get; set; } = new();
        public long Identity { get; set; } = 1;
        public GridWorld AOI { get; set; } = new(); //九宫格同步世界对象
        public NavmeshSystem Navmesh { get; set; } = new();
        public MyDictionary<Type, MyDictionary<byte, MethodInfo>> OperCmdMethods = new();

        public override void OnInit(params object[] args)
        {
            if (args == null || args.Length <= 0 || args[0] is not MapData mapData)
                return;
            AOI.Init(mapData.aoiData.offset, mapData.aoiData.cellSize, mapData.aoiData.worldSize);
            Navmesh.Init(mapData.navmeshData);
        }

        protected override void OnUpdate()
        {
            base.OnUpdate();
            AOI.Update();
            Navmesh.Update();
        }

        public void AddActor(T actor)
        {
            AddOperCommandHandler(actor, actor);
            AddEntity(actor);
            if (actor.Identity == 0U)
                actor.Identity = Identity++;
            Actors.Add(actor.Identity, actor);
            AOI.Insert(actor);
        }

        public void AddOperCommandHandler(T actor, object target)
        {
            var type = target.GetType();
            if (!OperCmdMethods.TryGetValue(type, out var commands))
            {
                var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                commands = new();
                foreach (var method in methods)
                {
                    var cmd = method.GetCustomAttribute<OperCmd>(true);
                    if (cmd == null)
                        continue;
                    commands.Add(cmd.cmd, method);
                }
                OperCmdMethods.Add(type, commands);
            }
            foreach (var command in commands)
            {
                var action = Delegate.CreateDelegate(typeof(OperCommandDelegate), target, command.Value);
                actor.Commands.Add(command.Key, (OperCommandDelegate)action);
            }
        }

        public bool TryGetActor<T1>(long identity, out T1 actor) where T1 : T
        {
            if (TryGetActor(identity, out T actorBase))
            {
                actor = actorBase as T1;
                return true;
            }
            actor = null;
            return false;
        }

        public bool TryGetActor(long identity, out T actor)
        {
            return Actors.TryGetValue(identity, out actor);
        }

        public T1 GetActor<T1>(long identity) where T1 : T
        {
            return GetActor(identity) as T1;
        }

        public T GetActor(long identity)
        {
            if (Actors.TryGetValue(identity, out T actor))
                return actor;
            return null;
        }

        public void RemoveActor(T actor, float time) => Event.AddEvent(time, () => RemoveActor(actor));

        public void RemoveActor(T actor) => RemoveActor(actor.Identity);

        public void RemoveActor(long identity) => TryRemoveActor<T>(identity, out _);

        public bool TryRemoveActor<T1>(long identity, out T1 actor) where T1 : T
        {
            if (Actors.TryRemove(identity, out T actorBase))
            {
                actorBase.Entity.Dispose();
                AOI.Remove(actorBase);
                actor = actorBase as T1;
                return true;
            }
            actor = null;
            return false;
        }

        public void Dispatch(in OperationList operList)
        {
            var operations = operList.operations;
            for (int i = 0; i < operations.Length; i++)
            {
                Dispatch(operations[i]);
            }
        }

        public void Dispatch(in Operation opt)
        {
            T actor;
            if (opt.cmd == OperCommand.EnterArea || opt.cmd == OperCommand.Spawn)
            {
                if (!TryGetActor(opt.identity, out actor))
                {
                    actor = Activator.CreateInstance(AssemblyHelper.GetHashType(opt.actorType)) as T;
                    actor.Identity = opt.identity;
                    actor.transform.Position = opt.position;
                    actor.transform.Rotation = opt.rotation;
                    AddActor(actor);
                }
                actor.OnOperation(opt);
                return;
            }
            if (TryGetActor(opt.identity, out actor))
            {
                actor.OnOperation(opt);
            }
        }
    }

    public class Scene : Scene<ActorBase> { }
}